package zhongbo.java.air.common.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.jsonwebtoken.*;
import lombok.Data;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 说明：
 *
 * @author zhongboWork
 * @version 1.0
 * @link <a href="http://www.manongjc.com/detail/12-qrmrfdqztkpwjus.html">jwt</a>
 * @since 2021-11-16 16:54
 */
public class JwtUtil {

    /**
     * Token过期时间必须大于生效时间
     */
    public static Long tokenExpireTime = 30 * 24 * 60 * 60 * 1000L;
    /**
     * Token加密解密的密码
     */
    private static String tokenSecret = "pwd";
    /**
     * 加密类型 三个值可取 HS256  HS384  HS512
     */
    private static final SignatureAlgorithm JWT_ALG = SignatureAlgorithm.HS256;
    /**
     * 添加一个前缀
     */
    private static String jwtSeparator = "Bearer#";
    /**
     * token生效时间(默认是从当前开始生效)
     * 默认：new Date(System.currentTimeMillis() + START_TIME)
     */
    private static Long startTime = 0L;
    /**
     * token在什么时间之前是不可用的（默认从当前时间）
     * 默认：new Date(System.currentTimeMillis() + BEFORE_TIME)
     */
    private static Long beforeTime = 0L;

    private static Key generateKey() {
        // 将将密码转换为字节数组
        /*import org.apache.commons.codec.binary.Base64
        byte[] bytes = Base64.decodeBase64(TOKEN_SECRET)*/
        byte[] bytes = tokenSecret.getBytes();
        // 根据指定的加密方式，生成密钥
        return new SecretKeySpec(bytes, JWT_ALG.getJcaName());
    }


    /**
     * 创建token
     *
     * @param sub token所面向的用户
     * @param aud 接收token的一方
     * @param jti token的唯一身份标识，主要用来作为一次性token,从而回避重放攻击
     * @param iss token签发者
     * @param map 自定义信息的存储
     * @return 加密后的token字符串
     */
    public static String createToken(String sub, String aud, String jti, String iss, Map<String, Object> map) {
        return createToken(sub, aud, jti, iss, map, beforeTime, startTime, tokenExpireTime);
    }

    /**
     * 创建token
     *
     * @param sub             token所面向的用户
     * @param aud             接收token的一方
     * @param jti             token的唯一身份标识，主要用来作为一次性token,从而回避重放攻击
     * @param iss             token签发者
     * @param map             自定义信息的存储
     * @param beforeTime      token在什么时间之前是不可用的（默认从当前时间）
     * @param startTime       token生效时间(默认是从当前开始生效)
     * @param tokenExpireTime Token过期时间必须大于生效时间
     * @return 加密后的token字符串
     */
    private static String createToken(String sub, String aud, String jti, String iss, Map<String, Object> map, Long beforeTime, Long startTime, Long tokenExpireTime) {
        final JwtBuilder builder = Jwts.builder();
        if (!map.isEmpty()) {
            builder.setClaims(map);
        }
        String token = builder
                .signWith(JWT_ALG, generateKey())
                .setSubject(sub)
                .setAudience(aud)
                .setId(jti)
                .setIssuer(iss)
                .setNotBefore(new Date(System.currentTimeMillis() + beforeTime))
                .setIssuedAt(new Date(System.currentTimeMillis() + startTime))
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpireTime))
                .compact();
        return jwtSeparator + token;
    }

    /**
     * 创建token
     *
     * @param sub token所面向的用户
     * @param aud 接收token的一方
     * @param map 自定义信息存储
     * @return token 字符串
     */
    public static String createToken(String sub, String aud, Map<String, Object> map) {
        return createToken(sub, aud, new Date().toString(), null, map);
    }

    /**
     * 创建token
     *
     * @param sub token所面向的用户    (用户名称)
     * @param map 自定义信息存储       (用户对象的Map存储格式)
     * @return token字符串
     */
    public static String createToken(String sub, Map<String, Object> map) {
        return createToken(sub, null, map);
    }

    /**
     * 创建token
     *
     * @param sub token所面向的用户
     * @return token字符串
     */
    public static String createToken(String sub) {
        return createToken(sub, null);
    }

    /**
     * 解析token
     * 可根据Jws<Claims>   获取  header|body|getSignature三部分数据
     * body: 包括存入的Map对象信息以及token一些额外信息的Map对象, 可以直接转换回JavaBean
     *
     * @param token token字符串
     * @return Jws
     */
    public static Jws<Claims> parseToken(String token) {
        // 移除 token 前的"Bearer#"字符串
        token = token.replaceFirst(jwtSeparator, "");
        // 解析 token 字符串
        return Jwts.parser().setSigningKey(generateKey()).parseClaimsJws(token);
    }

    /**
     * 校验token,校验是否是本服务器的token
     *
     * @param token token字符串
     * @return boolean
     */
    public static Boolean checkToken(String token) {
        return parseToken(token).getBody() != null;
    }

    /**
     * 根据sub判断token
     *
     * @param token token字符串
     * @param sub   面向的用户
     * @return boolean
     */
    public static Boolean checkToken(String token, String sub) {
        return parseToken(token).getBody().getSubject().equals(sub);
    }

    public static String getJwtSeparator() {
        return jwtSeparator;
    }

    public static void setTokenExpireTime(Long tokenExpireTime) {
        JwtUtil.tokenExpireTime = tokenExpireTime;
    }

    public static void setTokenSecret(String tokenSecret) {
        JwtUtil.tokenSecret = tokenSecret;
    }

    public static void setJwtSeparator(String jwtSeparator) {
        JwtUtil.jwtSeparator = jwtSeparator;
    }

    public static void setStartTime(Long startTime) {
        JwtUtil.startTime = startTime;
    }

    public static void setBeforeTime(Long beforeTime) {
        JwtUtil.beforeTime = beforeTime;
    }

    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        map.put("userId", "2342123123");
        map.put("userName", "zhangsan");
        //创建token
        final String token = createToken("2342123123", "aud", new Date().toString(), "MaiKeBai", map);
        System.out.println("token = " + token);


        //解析token
        final Jws<Claims> claimsJws = parseToken(token);

        //header
        final JwsHeader<?> header = claimsJws.getHeader();
        System.out.println("header.getAlgorithm() = " + header.getAlgorithm());
        System.out.println("header.getKeyId() = " + header.getKeyId());
        //body部分
        final Claims body = claimsJws.getBody();
        for (String s : body.keySet()) {
            System.out.print(s + "   ");
        }

        System.out.println("body.getSubject() = " + body.getSubject());
        System.out.println("body.getId() = " + body.getId());
        System.out.println("body.getExpiration() = " + body.getExpiration());
        System.out.println("body.getIssuedAt() = " + body.getIssuedAt());
        System.out.println("body.getNotBefore() = " + body.getNotBefore());
        System.out.println("body.getIssuer() = " + body.getIssuer());
        System.out.println("body.getAudience() = " + body.getAudience());

        //获取自定义信息
        System.out.println("body.get(\"userId\",String.class) = " + body.get("userId", String.class));
        System.out.println("body.get(\"userName\",String.class) = " + body.get("userName", String.class));

        //获取签名
        System.out.println("claimsJws.getSignature() = " + claimsJws.getSignature());

        User user = new User();
        user.setUserName("username123");
        user.setPassWord("pass123");
        user.setCreateTime(new Date());
        Map<String, Object> beanToMap = BeanUtil.beanToMap(user, false, true);
        String sub123 = createToken("sub123", beanToMap);
        Jws<Claims> parseToken = parseToken(sub123);
        Claims body1 = parseToken.getBody();
        System.out.println(body1);
        String jsonString = JSONUtil.toJsonStr(body1);
        JSONObject toJSON = JSONUtil.parseObj(body1);
        User user1 = JSONUtil.toBean(toJSON, User.class);
        User user2 = JSONUtil.toBean(jsonString, User.class);
        System.out.println(user1);
        System.out.println(user2);
        System.out.println("finished");
    }

    @Data
    private static class User {
        private String userId;
        private String userName;
        private String passWord;
        private Boolean isAdmin;
        private Date createTime;
        private Date updateTime;
    }
}
